﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace STSdb.MTL
{
    /// <summary>
    /// Static class helps to handle relations between engines which must have mutual transactions.
    /// Steps:
    /// 1. We create Database which will store info about distributed transactions, it has to be new database, which can serve transactions between unlimited engines:
    /// 
    ///     DistributedTransactionCoordinator.CreateDistributedTransactionsDb("pathToSpecificDb");
    ///     
    /// 2. Create multiple engines (standard way)
    /// try
    /// {
    ///    engine1 = STSdbMTLEngine();
    ///    engine2 = STSdbMTLEngine();
    ///    
    ///    //in the end (when ALL engines are initialized) is necessary to make such operation. 
    ///    //It will guarantee that all engines have started correctly and we don't have any Failed Transactions to restore any more.
    ///    
    ///    DistributedTransactionCoordinator.DeleteFailedTransactionsRecords();
    /// }
    /// catch
    /// {
    ///   //if something happens here, we will need detailed analysis of that.
    /// }
    /// 
    /// 3. Make multi-engines transaction:
    /// 
    ///    using(var tran1 = engine1.GetTransaction())
    ///    {
    ///         using(var tran2 = engine2.GetTransaction())
    ///         {
    ///             ... do something with tran1 tables
    ///             ... do something with tran2 tables 
    ///             ... etc
    /// 
    ///             //DON'T USE STANDARD COMMIT, BUT USE
    /// 
    ///             DistributedTransactionCoordinator.CommitDistributedTransactions(tran1,tran2);
    ///         }
    ///    }
    /// </summary>
    public static class DistributedTransactionCoordinator
    {
        static long baseValue = DateTime.Now.Ticks;
        static object sync_dt = new object();
        static STSdb.MTL.STSdbMTLengine transactionsDb = null;

        /// <summary>
        /// Returns Transaction Id
        /// </summary>
        public static long GetDistributedTransactionId
        {
            get
            {
                lock (sync_dt)
                {
                    baseValue += 1;
                    return baseValue;
                }
            }
        }
                
        /// <summary>
        /// Creates database which will store info about distributed transactions.
        /// Only interesting in case if we are going to use distributed transactions
        /// </summary>
        /// <param name="dbPath"></param>
        /// <returns></returns>
        public static STSdb.MTL.STSdbMTLengine CreateDistributedTransactionsDb(string dbPath)
        {
            transactionsDb = new STSdbMTLengine(dbPath);
            return transactionsDb;
        }


        static object dispose_lock = new object();
        /// <summary>
        /// Internal technical function
        /// </summary>
        public static void DisposeDistributedTransactionDb()
        {
            lock (dispose_lock)
            {
                if (transactionsDb == null)
                    return;

                transactionsDb.Dispose();
                transactionsDb = null;
            }
        }

        /// <summary>
        /// Returns list of started and not finished transactions. 
        /// Is used automaticall when is initialized any of the STSdb Engines and Transactions Db is supplied to DistributedTransactionCoordinator.
        /// </summary>
        /// <returns></returns>
        public static List<long> GetFailedTransactionsList()
        {
            if (transactionsDb == null)
                return null;

            using (var tran = transactionsDb.GetTransaction())
            {
                return tran.SelectBackward<long, byte>("@sys_dstTranHeader", eSelectMode.READ).Select(r => r.Key).ToList();
            }
        }

        /// <summary>
        /// In case of successfull start of all engines, using this function, we can clear table which stores list of non-finished transactions
        /// </summary>
        public static void DeleteFailedTransactionsRecords()
        {
            if (transactionsDb == null)
                return;

            using (var tran = transactionsDb.GetTransaction())
            {
                tran.DeleteAll<long, byte>("@sys_dstTranHeader");
            }
        }


        private static void SaveTransactionHeader(long transactionId)
        {
            using(var tran = transactionsDb.GetTransaction())
            {
                //DstTranHeader header=new DstTranHeader()
                //{
                //     TransactionId = transactionId
                //};

                //tran.Insert<long, DstTranHeader>("@sys_dstTranHeader", transactionId, header);
                tran.Insert<long, byte>("@sys_dstTranHeader", transactionId, 1);
                tran.Commit();
            }
        }

        private static void DeleteTransactionHeader(long transactionId)
        {
            using (var tran = transactionsDb.GetTransaction())
            {               
                //tran.Delete<long, DstTranHeader>("@sys_dstTranHeader", transactionId);
                tran.Delete<long, byte>("@sys_dstTranHeader", transactionId);
                tran.Commit();
            }
        }



        public static void CommitDistributedTransactions(params STSdb.MTL.Transaction[] transactions)
        {
            //!!! MUST BE A CHECK, IF NOT SPECIFIED TransactionsDb then CommitDistributedTransactions must fail with an exception

            if(transactionsDb == null)
                throw new Exception("STSdb.MTL.DistributedTransactionCoordinator.CommitDistributedTransactions: Transaction Db is not supplied!");

            if(transactions == null)
                throw new Exception("STSdb.MTL.DistributedTransactionCoordinator.CommitDistributedTransactions: There is no supplied transactions to operate with!");

            //If only one transaction - make standard Commit.
            if (transactions.Count() == 1)
            {
                transactions[0].Commit();
                return;
            }


            long transactionId = GetDistributedTransactionId;

            List<STSdb.MTL.Transaction> successfullTransactions = new List<Transaction>();
            
            //saving distributed transaction header info into third DB special for storing Distributed Transaction Header for many instances.
            try
            {
                SaveTransactionHeader(transactionId);
            }
            catch (Exception ex)
            {
                throw new Exception("STSdb.MTL.DistributedTransactionCoordinator.CommitDistributedTransactions: Distributed Transaction Header save has failed!");
            }

            //Iterating through all transactions.
            //For every transaction, the list of transacted tabels will be taken,
            //For every transacted table snapshot inside of the engine will be made and saved in format of
            //"@sys_trs_" + transactionId.ToString() + "_" + this._tableName
            //Then all tables after snapshot will be put into DstTranInfo class
            //This class will be saved into @sys_dstTranInfo table.
            //If one of sequential operations fails - jumps to //CATCH 1 point. There we will be able to restore one by one all transacted tables.
            //If PC loses power (is damaged or so) then, after engine restart, first of all we check the existence of the records inside of @sys_dstTranHeader.
            //If records are found, it will mean not finished transactions and, right after engine start, we iterate through Every Started Engine
            //@sys_dstTranInfo with the found transaction key and restoring back tables from snapshots. Then DELETE RECORD IN @sys_dstTranHeader.
            //For remote engines, the not finished transaction id must be sent via wires before initiation remote engine instance for the same operation.
            //When engine restores all tables it deletes record in @sys_dstTranInfo - restore and delete must be one transaction.

            //TimeOut
            //If one table in DT dont start the snapshotting, make usual COmmit
            //If exists table when engine restarts
            //Multi files engine start

            foreach (var tran in transactions)
            {
                try
                {
                    tran.TransactionDistributedSupport.DistributedCommitStart(transactionId);
                    successfullTransactions.Add(tran);
                }
                catch (Exception ex)
                {//CATCH 1

                    bool succesfullyRestored = true;

                    //try to restore back successfullTransactions tables
                    foreach (var stran in successfullTransactions)
                    {
                        try
                        {
                            stran.TransactionDistributedSupport.DistributedRollback(transactionId);
                        }
                        catch
                        {
                            succesfullyRestored = false;
                        }                        
                    }

                    //deleting transaction header if all succussfully committed transactions were successfully restored back.
                    if (succesfullyRestored)
                    {
                        try
                        {
                            DeleteTransactionHeader(transactionId);
                        }
                        catch
                        {
                            //this exception must bring to the Engine Failure           !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                        }
                    }
                    
                    throw new Exception("STSdb.MTL.DistributedTransactionCoordinator.CommitDistributedTransactions: Commit failed");                    
                }
                
            }

            //IN THE END, IF COME TO THIS POINT, ALL In-Memory Read locators must be swapped to write locators. ONLY HERE.
            //DELETE RECORD ABOUT TRANSACTION
            try
            {
                DeleteTransactionHeader(transactionId);
            }
            catch (Exception ex)
            {                
                //this exception must bring to the Engine Failure           !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            }


            //!!!!!!!!!!!!!!!!!!!!!!!1  HERE SWAP READ LCOATORS ONLY for all transactions
            foreach (var tran in transactions)
            {
                //change in memory READ locators on Write for all write tables for every of the transactions
                tran.TransactionDistributedSupport.DistributedCommitEnd();
            }

            //DELETE RECORD IN @sys_dstTranHeader if all is ok                  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

        }



    }
}
